package interpreter

import (
	"io"
	"fmt"
	"kumachan/standalone/util"
	"kumachan/standalone/util/fatal"
	"kumachan/standalone/util/richtext"
	"kumachan/lang/source"
	"kumachan/lang/typsys"
	"kumachan/lang/textual/ast"
	"kumachan/lang/textual/syntax"
	"kumachan/lang/textual/parser"
	"kumachan/lang/textual/transformer"
	"kumachan/interpreter/core"
	"kumachan/interpreter/compiler"
	"kumachan/interpreter/program"
	"kumachan/interpreter/runtime"
	"kumachan/interpreter/runtime/debug"
)


type ReplConfig struct {
	Input   io.Reader
	Output  io.Writer
}

func CreateBlankProgram() (*program.Program, *compiler.NsHeaderMap, *compiler.Executable, fatal.Exception) {
	var project = compiler.Project {}
	var fs = compiler.RealFileSystem {}
	var meta = program.Metadata { ProgramPath: "." }
	var p, ctx, exe, errs1, errs2 = compiler.Compile(project, fs, meta)
	if errs1 != nil { return nil, nil, nil, errs1 }
	if errs2 != nil { return nil, nil, nil, errs2 }
	return p, ctx, exe, nil
}

func Repl(p *program.Program, ctx *compiler.NsHeaderMap, exe *compiler.Executable, env *runtime.Environment, cfg ReplConfig) {
	var main_exit = make(runtime.ExitSignal)
	var repl_exit = make(chan struct{})
	var h, _ = runtime.Run(p, "", env, main_exit)
	var input = cfg.Input
	var output = cfg.Output
	var inspect_ctx = debug.MakeInspectContext(ctx)
	go (func() {
		var eventloop = h.EventLoop()
		var eval_ctx = program.CreateEvalContext(h)
		var bindings = make([] *program.Binding, 0)
		var bind = func(name string, expr *program.Expr, value core.Object) {
			var binding = &program.Binding {
				Name:     name,
				Type:     expr.Type,
				Location: expr.Info.Location,
			}
			bindings = append(bindings, binding)
			eval_ctx = eval_ctx.NewCtxBind(binding, value)
		}
		const repl_title = "** REPL **"
		fmt.Fprintf(output, "%s\n\n", repl_title)
		var cmd_id = uint(0)
		for {
			cmd_id += 1
			var code, ok = replReadLine(input, output, cmd_id)
			if !(ok) {
				// EOF
				close(repl_exit)
				return
			}
			if len(code) == 0 {
				cmd_id -= 1
				continue
			}
			var cmd_label_err = fmt.Sprintf("\033[31m(%d)\033[0m", cmd_id)
			var cmd_ast_name = fmt.Sprintf("[%d]", cmd_id)
			const repl_root = syntax.ReplRootPartName
			cst, p_err := parser.Parse(code, repl_root, cmd_ast_name)
			if p_err != nil {
				fmt.Fprintf(output,
					"[%d] error:\n%s\n", cmd_id, replText(p_err.Message()))
				continue
			}
			var key = source.FileKey {
				Context: "repl",
				Path:    cmd_ast_name,
			}
			var cmd = transformer.Transform(cst, key).(ast.ReplRoot)
			var expr_node = ast.ReplCmdGetExpr(cmd.Cmd)
			var expr, err = compiler.CompileExpr (
				expr_node, ctx, exe, bindings, "", nil,
			)
			if err != nil {
				fmt.Fprintf(output,
					"[%d] error:\n%s\n", cmd_id, replText(err.Message()))
				continue
			} else {
				goto ok
			}
			ok:
			var temp_name = fmt.Sprintf("temp%d", cmd_id)
			var value = eval_ctx.Eval(expr)
			bind(temp_name, expr, value)
			replInspect(value, expr.Type, inspect_ctx, output, cmd_id)
			switch cmd := cmd.Cmd.(type) {
			case ast.ReplAssign:
				var specified_name = source.CodeToString(cmd.Name.Name)
				bind(specified_name, expr, value)
			case ast.ReplRun:
				var o, ok = (*value).(core.Observable)
				if ok {
					var v_type = replMatchObservable(expr.Type.Type)
					replRun(o, eventloop, output, v_type, inspect_ctx, cmd_id)
				} else {
					fmt.Fprintf(output,
						"%s\nrun: expect Observable\n\n", cmd_label_err)
				}
			case ast.ReplEval:
				// do nothing extra
			}
		}
	})()
	<- main_exit
	<- repl_exit
}

func replText(msg richtext.Block) string {
	return msg.RenderConsole()
}

func replReadLine(input io.Reader, output io.Writer, cmd_id uint) (source.Code, bool) {
	{ var _, err = fmt.Fprintf(output, "\033[1m[%d]\033[0m ", cmd_id)
	if err != nil { panic(err) } }
	var code_str, _, err = util.WellBehavedFscanln(input)
	if err != nil {
		if err == io.EOF {
			fmt.Fprintf(output, "\n")
			return nil, false
		} else {
			panic(err)
		}
	}
	return source.StringToCode(code_str), true
}

func replInspect(value core.Object, t typsys.CertainType, ctx debug.InspectContext, output io.Writer, cmd_id uint) {
	var cmd_label = fmt.Sprintf("\033[34m(%d)\033[0m", cmd_id)
	var msg = debug.Inspect(value, t.Type, ctx)
	var text = msg.RenderConsole()
	fmt.Fprintf(output,
		"%s\n%s\n",
		cmd_label, text)
}

func replMatchObservable(t typsys.Type) typsys.Type {
	var v_type, _ = program.T_Observable_(t)
	return v_type
}

func replRun(
	o          core.Observable,
	eventloop  *core.EventLoop,
	output     io.Writer,
	v_type     typsys.Type,
	ctx        debug.InspectContext,
	cmd_id     uint,
) {
	var cmd_label_ok = fmt.Sprintf("\033[32m(%d)\033[0m", cmd_id)
	var cmd_label_err = fmt.Sprintf("\033[31m(%d)\033[0m", cmd_id)
	var ch_values = make(chan core.Object, 1024)
	var ch_error = make(chan error, 1)
	core.Run(o, eventloop, core.DataSubscriber {
		Values: ch_values,
		Error:  ch_error,
	})
	for {
		select {
		case v, not_closed := <- ch_values:
			if not_closed {
				var msg = debug.Inspect(v, v_type, ctx)
				_, err := fmt.Fprintf(output,
					"%s <value>\n%s\n", cmd_label_ok, replText(msg))
				if err != nil { panic(err) }
			} else {
				_, err := fmt.Fprintf(output,
					"%s <terminated> completed\n\n", cmd_label_ok)
				if err != nil { panic(err) }
				return
			}
		case e, not_closed := <- ch_error:
			if not_closed {
				var e_obj = core.Obj(core.Error { Value: e })
				var msg = debug.Inspect(e_obj, program.T_Error(), ctx)
				_, err := fmt.Fprintf(output,
					"%s <error>\n%s\n", cmd_label_err, replText(msg))
				if err != nil { panic(err) }
			} else {
				_, err := fmt.Fprintf(output,
					"%s <terminated> error occurred\n\n", cmd_label_err)
				if err != nil { panic(err) }
				return
			}
		}
	}
	return
}


